﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using Silvermoon.Controls.Interfaces;
using System.Diagnostics;

namespace Silvermoon.Controls.Base
{

    /// <summary>
    /// Collection of Controls as single linked list.
    /// </summary>
    public class ControlCollection : IEnumerable<Control>
    {
        public ControlCollection(Control parent)
            : base()
        {
            this.Parent = parent;
            notify = parent as INotifyControlCollection;
        }

        private INotifyControlCollection notify;

        /// <summary>
        /// Gets the Parent of the child controls in the collection.
        /// </summary>
        public Control Parent { get; internal set; }

        /// <summary>
        /// Gets the first control in the collection.
        /// </summary>
        public Control First { get; internal set; }

        /// <summary>
        /// Gets the last control in the list.
        /// </summary>
        public Control Last { get; private set; }

        /// <summary>
        /// Gets whether the collection is empty.
        /// </summary>
        public bool IsEmpty
        {
            get { return First == null; }
        }

        /// <summary>
        /// Gets whether the collection has more than one control.
        /// </summary>
        public bool HasMany
        {
            get { return First != null && First != Last; }
        }

        public void Add(params Control[] controls)
        {
            foreach (Control c in controls) AddLast(c);
        }

        /// <summary>
        /// Adds a control at the end of the collection.
        /// </summary>
        /// <param name="control">Control to add.</param>
        public void Add(Control control)
        {
            AddLast(control);
            if (notify != null) notify.ControlAdded(control);
        }

        /// <summary>
        /// Adds a control at the end of the collection.
        /// </summary>
        /// <param name="control">Control to add.</param>
        public void AddLast(Control control)
        {
            control.NextControl = null;
            control.Parent = Parent;

            Control last = Last;
            if (last == null)
            {
                Last = First = control;
            }
            else
            {
                last.NextControl = control;
                Last = control;
            }
            if (notify != null) notify.ControlAdded(control);
        }

        /// <summary>
        /// Adds a control at the beginning of the collection.
        /// </summary>
        /// <param name="control">Control to add.</param>
        public void AddFirst(Control control)
        {
            CheckParentParent(control);
            control.Parent = Parent;

            Control first = First;
            if (first == null)
            {
                control.NextControl = null;
                First = Last = control;
            }
            else
            {
                control.NextControl = first;
                First = control;
            }
            if (notify != null) notify.ControlAdded(control);
        }

        [Conditional("DEBUG")]
        private void CheckParentParent(Control control)
        {
            if (control != null)
            {
                Control parent = control.Parent;
                if (parent != null)
                {
                    throw new ArgumentOutOfRangeException("Parent must be null.");
                }
            }
        }

        /// <summary>
        /// Removes a control from the collection.
        /// </summary>
        /// <param name="control">Control to remove.</param>
        public void Remove(Control control)
        {
            if (control == null) return;
            control.Parent = null;
            Control prev = First;
            if (prev == null) return;
            if (prev == control)
            {
                First = control.NextControl;
                control.NextControl = null;
                if (control == Last) Last = null;
            }
            else
            {
                Control current = prev.NextControl;
                while (current != null)
                {
                    if (current == control)
                    {
                        prev.NextControl = control.NextControl;
                        if (control == Last) Last = prev;
                        return;
                    }
                    prev = current;
                    current = current.NextControl;
                }
            }
            if (notify != null) notify.ControlRemoved(control);
        }

        /// <summary>
        /// Replaces a control by another.
        /// </summary>
        /// <param name="controlToReplace">Control to be replaced.</param>
        /// <param name="replacingControl">Control that replaces  the control.</param>
        public void Replace(Control controlToReplace, Control replacingControl)
        {
            replacingControl.Parent = Parent;
            replacingControl.NextControl = controlToReplace.NextControl;
            controlToReplace.Parent = null;

            if (controlToReplace == First)
            {
                First = replacingControl;
            }
            else
            {
                Control first = First;
                while (first != null)
                {
                    Control next = first.NextControl;
                    if (next == controlToReplace)
                    {
                        first.NextControl = replacingControl;
                        break;
                    }
                    first = next;
                }
            }
            if (notify != null)
            {
                notify.ControlRemoved(controlToReplace);
                notify.ControlAdded(replacingControl);
            }
        }

        /// <summary>
        /// Appends a control after another control.
        /// </summary>
        /// <param name="fromControl">Control after where to append.</param>
        /// <param name="controlToAppend">Control to append.</param>
        public void Append(Control fromControl, Control controlToAppend)
        {
            controlToAppend.Parent = Parent;
            if (fromControl.NextControl == null)
            {
                Last = controlToAppend;
                fromControl.NextControl = controlToAppend;
            }
            else
            {
                Control next = fromControl.NextControl;
                controlToAppend.NextControl = next;
                fromControl.NextControl = controlToAppend;
                if (next == Last) Last = controlToAppend;
            }
            if (notify != null) notify.ControlAdded(controlToAppend);
        }

        /// <summary>
        /// Preprends a control before another control.
        /// </summary>
        /// <param name="fromControl">Control from where to prepend.</param>
        /// <param name="controlToPrepend">Control to prepend.</param>
        public void Prepend(Control fromControl, Control controlToPrepend)
        {
            controlToPrepend.Parent = Parent;
            if (fromControl == First)
            {
                First = controlToPrepend;
                controlToPrepend.NextControl = fromControl;
            }
            else
            {
                Control first = First;
                while (first != null)
                {
                    Control next = first.NextControl;
                    if (next == fromControl)
                    {
                        controlToPrepend.NextControl = fromControl;
                        first.NextControl = controlToPrepend;
                        return;
                    }
                    first = next;
                }
            }
            if (notify != null) notify.ControlAdded(controlToPrepend);
        }

        /// <summary>
        /// Clears the collection and sets the Parent property of all controls in the list to null.
        /// See also <seealso cref="ClearQuick."/>
        /// </summary>
        public void Clear()
        {
            Control c = First;
            while (c != null)
            {
                Control next = c.NextControl;
                c.NextControl = null;
                c.Parent = null;
                c = next;
            }
            First = Last = null;
            if (notify != null) notify.CollectionCleared();
        }

        /// <summary>
        /// Clears the collection and sets the Parent property of all controls in the list to null.
        /// See also <seealso cref="ClearQuick."/>
        /// </summary>
        public void ClearAndDispose()
        {
            Control c = First;
            while (c != null)
            {
                Control next = c.NextControl;
                c.NextControl = null;
                c.Parent = null;
                c.Dispose();
                c = next;
            }
            First = Last = null;
            if (notify != null) notify.CollectionCleared();
        }

        /// <summary>
        /// Quickly clears the collection without setting the Parent property of each control in the collection to null.
        /// This is significantly faster than <see cref="Clear"/> as it does not perform a loop over all controls in the collection.
        /// </summary>
        public void ClearQuick()
        {
            First = Last = null;
            if (notify != null) notify.CollectionCleared();
        }


        /// <summary>
        /// Adds a range of control at the end of the collection.
        /// </summary>
        /// <param name="controls">Array of controls to add.</param>
        [Obsolete("Use Add().")]
        public void AddRange(params Control[] controls)
        {
            foreach (Control c in controls) AddLast(c);
        }



        /// <summary>
        /// Gets whether the collection contains a given controls.
        /// </summary>
        /// <param name="control">Control to check whether it is in the collection.</param>
        /// <returns>True, if the control is in the collection, otherwise false.</returns>
        public bool Contains(Control control)
        {
            if (Last == control) return true;
            Control c = First;
            while (c != null)
            {
                if (c == control) return true;
                c = c.NextControl;
            }
            return false;
        }

        #region IEnumerable<Control> Members

        public IEnumerator<Control> GetEnumerator()
        {
            return new Enumerator(this);
        }

        class Enumerator : IEnumerator<Control>
        {
            private ControlCollection collection;
            private Control current;
            private bool start;

            public Enumerator(ControlCollection collection)
                : base()
            {
                this.collection = collection;
                Reset();
            }

            #region IEnumerator<Control> Members

            public Control Current
            {
                get { return current; }
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return current; }
            }

            public bool MoveNext()
            {
                if (!start)
                {
                    current = collection.First;
                    start = true;
                }
                else
                {
                    current = current != null ? current.NextControl : null;
                }
                return current != null;
            }

            public void Reset()
            {
                start = false;
                current = collection.First;
            }

            #endregion
        }


        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new Enumerator(this);
        }

        #endregion
    }
}
